/**
 * 
 */
package com.bloomingstars.sqm;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.opencsv.CSVReader;
import com.opencsv.CSVWriter;

/**
 * @author Emmanuele Sordini
 *
 */
public class SqmReadingManager {

    public static final long CSF_TIMESPAN = 600; // CSF timespan (secs)
    
    private static final Logger logger = LogManager.getLogger(SqmReadingManager.class);
    
    private int readingMaxCount;
    private String csvFile;
    private List<SqmReadingRecord> readings = new ArrayList<SqmReadingRecord>();
    
    public SqmReadingManager(int readingCount, String csvFile) {
        super();
        this.readingMaxCount = readingCount;
        this.csvFile = csvFile;
    }
        
    public void readFile() throws IOException {
        File file = new File(csvFile);
        
        if (file.exists()) {
            // Try reading file
            CSVReader reader = new CSVReader(new FileReader(file), ',');            
            List<String[]> fileContent = reader.readAll();            
            reader.close();
            
            logger.info("CSV file content read");
            
            for (int i=0; i<fileContent.size(); i++) {
                SqmReadingRecord record = parseStringArray(fileContent.get(i), i);
                
                if (record != null) {
                    addReading(record);
                } else {
                    logger.warn("Reading " + i + "has errors in one or more fields. Skipping...");                    
                }
            }
            
            trimRecords(86400.0);
        }
    }
    
    public void trimRecords(double lastTimespan) {        
        if (readings.size() > 2) {
            int trimIndex = -1;
            boolean found = false;
            long timeCursor = readings.get(readings.size() - 1).getTime().getTimeInMillis();                    
            for (int i=readings.size()-2; i>=0 && !found; i--) {
                double elapsedBackwards = (timeCursor - readings.get(i).getTime().getTimeInMillis())/1000.0;
                if (elapsedBackwards > lastTimespan) {
                    trimIndex = i;
                    found = true;
                }
            }            
            
            if (trimIndex > -1) {
                for (int i=0; i<=trimIndex; i++) {
                    readings.remove(0);
                }
            }
        }
    }
    
    public void writeFile() throws IOException {
        File file = new File(csvFile);
        
        List<String[]> fileContent = new ArrayList<String[]>();
        
        for (SqmReadingRecord record: readings) {
            fileContent.add(parseRecord(record));
        }
        
        CSVWriter writer = new CSVWriter(new FileWriter(file), ',');
        writer.writeAll(fileContent);
        writer.close();        
    }
    
    public void addReading(SqmReadingRecord record) {
        readings.add(record);        
        while (readings.size() > readingMaxCount) readings.remove(0);
    }
    
    public boolean canCalcCsf() {
//        int readingCount =  (int)CSF_TIMESPAN / freq;
//        boolean sizeFlag = readings.size() > readingCount;
//        
//        if (!sizeFlag || freq > 60) return false;
//        
//        SqmReadingRecord record = readings.get(readings.size() - readingCount);
//        long elapsed = record.getTime().getTimeInMillis() - readings.get(readings.size() - 1).getTime().getTimeInMillis();
//        
//        if (Math.abs(elapsed-CSF_TIMESPAN) < 120)
//            return true;
//        else
//            return false;
        int lookAheadIndex = calcCsfLookBackIndex();
        return (readings.size() - lookAheadIndex + 1) >=10;
    }
    
    public List<SqmReadingRecord> getReadings() {
        return readings;
    }
    
    public void setReadings(List<SqmReadingRecord> readingRecords) {
        readings = readingRecords;
    }
    
    private int calcCsfLookBackIndex() {
        long startTime = readings.get(readings.size()-1).getTime().getTimeInMillis();
        int foundIndex = -1;
        boolean done = false;
        for (int i=readings.size()-1; i>=0 && !done; i--) {
            long elapsed = Math.abs(startTime-readings.get(i).getTime().getTimeInMillis());
            if (elapsed >= CSF_TIMESPAN)
            {
                done = true;
                foundIndex = i;
            }
        }
        
        return foundIndex;
    }
    
    public double calcCsf() {
        List<SqmReadingRecord> csfRecords = new ArrayList<SqmReadingRecord>();
        long intv = CSF_TIMESPAN/10;
        
        long timeCursor = readings.get(readings.size()-1).getTime().getTimeInMillis();
        csfRecords.add(readings.get(readings.size()-1));
        int index = readings.size()-1;
        while (csfRecords.size() < 10) {
            while (timeCursor - readings.get(index).getTime().getTimeInMillis()<intv) index--;
            index++;
            csfRecords.add(readings.get(index));
            timeCursor = readings.get(index).getTime().getTimeInMillis();
        }
        
        double csf = 0.0;
        for (int i=0; i<9; i++) {
            csf += Math.abs(csfRecords.get(i+1).getSqmReading() - csfRecords.get(i).getSqmReading());
        }
//        SqmReadingRecord[] csfReadings = new SqmReadingRecord[10];
//        
//        int step = (int)CSF_TIMESPAN / freq / 10;
//        
//        
//        for (int i=0; i<10; i++) {
//            int index = readings.size() - 1 - step*i;
//            csfReadings[i] = readings.get(index);
//            logger.debug("Reading record chosen for CSF: " + index);
//        }
//        for (int i=0; i<10; i++) {
//            float absDelta = Math.round((float)i * 60.0/freq);
//            SqmReadingRecord record = readings.get(readings.size() - 1 - (int)absDelta);
//            csfReadings[i] = record;
//            logger.debug("Reading record chosen for CSF: " + (readings.size() - (int)absDelta));
//        }

        return csf;
    }
    
    private SqmReadingRecord parseStringArray(String[] arr, int i) {
        SqmReadingRecord record = null;
        if (arr.length == SqmReadingRecord.getValidFieldCount()) {
            Calendar time = null;
            double mpsas = 0.0, temp = 0.0;
            try {
                long ms = Long.parseLong(arr[0]);
                time = Calendar.getInstance();
                time.setTimeInMillis(ms);
            } catch (NumberFormatException ex) {
                logger.warn("Error parsing date field");
                return null;
            }
            
            try {
                mpsas = Double.parseDouble(arr[1]);
            } catch (NumberFormatException ex) {
                logger.warn("Error parsing SQM reading field");
                return null;                
            }
            
            try {
                temp = Double.parseDouble(arr[2]);
            } catch (NumberFormatException ex) {
                logger.warn("Error parsing temperature field");
                return null;                
            }   
            
            record = new SqmReadingRecord(time, mpsas, temp);
        } else {
            logger.warn("Record <" + i + "> has " + arr.length + 
                        "fields, which is not consistent with the expected number (" + 
                        SqmReadingRecord.getValidFieldCount() + ")");
        }
        
        return record;
    }
    
    
    private String[] parseRecord(SqmReadingRecord record) {
        String[] arr = new String[SqmReadingRecord.getValidFieldCount()];
        
        arr[0] = Long.toString(record.getTime().getTimeInMillis());
        arr[1] = Double.toString(record.getSqmReading());
        arr[2] = Double.toString(record.getTemperature());
        
        return arr;
    }
}
